<template>
  <div class="bezier-selector">
    <svg id="canvas" height="200" width="200">
      <g transform="scale(1, -1) translate(0, -200)">
        <path id="bezier" fill="transparent" stroke="blue" stroke-width="2"/>
        <circle id="p0" class="point" cx="0" cy="0" fill="rgba(114,204,255,.6)" r="8"/>
        <circle id="p1" class="point" cx="50" cy="150" fill="rgba(134,134,134,.6)" r="8"/>
        <circle id="p2" class="point" cx="150" cy="50" fill="rgba(134,134,134,.6)" r="8"/>
        <circle id="p3" class="point" cx="200" cy="200" fill="rgba(114,204,255,.6)" r="8"/>
      </g>
    </svg>
    <div>
      p0:<span id='p0v'></span><br/>
      p1:<span id='p1v'></span><br/>
      p2:<span id='p2v'></span><br/>
      p3:<span id='p3v'></span><br/>
    </div>
    <button @click="init()">
      初始化
    </button>
  </div>
</template>

<script setup>
import {onMounted, reactive} from 'vue'

// 注册一个事件
const emit = defineEmits(['controllerPointChange'])

const pageData = reactive({
  p1point: undefined,
  p2point: undefined,
  canvas: undefined,
  points: [],
  needPush: false,
  lastPush: Date.now(),
  bezierPath: undefined,
  selected: undefined,
  p0input: undefined,
  p1input: undefined,
  p2input: undefined,
  p3input: undefined,
  p1Factor: [],
  p2Factor: [],
});

onMounted(() => {
  pageData.p1point = document.getElementById("p1");
  pageData.p2point = document.getElementById("p2");
  pageData.canvas = document.getElementById("canvas");
  pageData.points = document.querySelectorAll(".point");
  pageData.bezierPath = document.getElementById("bezier");
  pageData.elected = null;
  pageData.p0input = document.getElementById("p0v");
  pageData.p1input = document.getElementById("p1v");
  pageData.p2input = document.getElementById("p2v");
  pageData.p3input = document.getElementById("p3v");

  pageData.points.forEach(point => {
    point.addEventListener("mousedown", e => {
      pageData.selected = e.target;
    });
  });

  document.addEventListener("mousemove", e => {
    if (pageData.selected) {
      const rect = pageData.canvas.getBoundingClientRect();
      pageData.selected.cx.baseVal.value = e.clientX - rect.left;
      pageData.selected.cy.baseVal.value = rect.bottom - e.clientY;
      updatePath();
    }
  });

  document.addEventListener("mouseup", () => {
    pageData.needPush = true;
    if (pageData.selected) {
      updatePath();
    }
    pageData.selected = null
  });
  updatePath();
})


function bezierCubic(p0, p1, p2, p3, t) {
  const t2 = t * t, t3 = t2 * t, oneMinusT = 1 - t, oneMinusT2 = oneMinusT * oneMinusT;
  return oneMinusT2 * oneMinusT * p0 + 3 * oneMinusT2 * t * p1 + 3 * oneMinusT * t2 * p2 + t3 * p3;
}

function updatePath() {
  const [p0, p1, p2, p3] = [...pageData.points].map(p => [p.cx.baseVal.value, p.cy.baseVal.value]);
  let pathData = `M ${p0[0]},${p0[1]}`;

  pageData.p0input.innerText = ` ${(p0[0] / 200).toFixed(4)}, ${(p0[1] / 200).toFixed(4)}`
  pageData.p1input.innerText = ` ${(p1[0] / 200).toFixed(4)}, ${(p1[1] / 200).toFixed(4)}`
  pageData.p2input.innerText = ` ${(p2[0] / 200).toFixed(4)}, ${(p2[1] / 200).toFixed(4)}`
  pageData.p3input.innerText = ` ${(p3[0] / 200).toFixed(4)}, ${(p3[1] / 200).toFixed(4)}`

  if (pageData.needPush || Date.now() - pageData.lastPush > 10) {
    emit('controllerPointChange', {
      p1: [(p1[0] / 200).toFixed(4), (p1[1] / 200).toFixed(4)],
      p2: [(p2[0] / 200).toFixed(4), (p2[1] / 200).toFixed(4)]
    })
    pageData.needPush = false;
    pageData.lastPush = Date.now()
  }

  //console.debug(p1[0].toFixed(2) + "," + p1[1].toFixed(2))

  for (let x = p0[0]; x <= p3[0]; x += 2) { // x 递增
    const t = findTForX(x, p0[0], p1[0], p2[0], p3[0]); // 反查 t
    const y = bezierCubic(p0[1], p1[1], p2[1], p3[1], t);
    pathData += ` L ${x},${y}`;
    //console.log('选择器坐标渲染:' + `${x.toFixed(4)}, ${y.toFixed(4)}`)
  }

  pageData.bezierPath.setAttribute("d", pathData);
}

function findTForX(xTarget, p0, p1, p2, p3) {
  let tMin = 0, tMax = 1, tMid;
  for (let i = 0; i < 20; i++) { // 二分查找提高精度
    tMid = (tMin + tMax) / 2;
    const x = bezierCubic(p0, p1, p2, p3, tMid);
    if (Math.abs(x - xTarget) < 0.05) return tMid;
    if (x < xTarget) tMin = tMid;
    else tMax = tMid;
  }
  return tMid;
}

const init = () => {
  pageData.p1point.cx.baseVal.value = 50
  pageData.p1point.cy.baseVal.value = 150
  pageData.p2point.cx.baseVal.value = 150
  pageData.p2point.cy.baseVal.value = 50
  pageData.needPush = true;
  updatePath();
}
</script>

<style scoped>
.bezier-selector {
  padding: 5px;
  margin: 0 auto;
  user-select: none;
  background-color: rgb(245, 245, 245);
  box-sizing: border-box;
  border-radius: 5px;
  font-size: 15px;
  color: #2d2c2c;
}

body {
  text-align: center;
}

svg {
  border: 1px solid #000;
}

.point {
  cursor: grab;
}
</style>
